﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using Mersiv.Lib.Wrapper.Event;

namespace Mersiv.Lib.Wrapper.Command
{

  /// <summary>
  /// A wrapper to manage the execution of a Mercurial HG.exe command.
  /// </summary>
  public class HgCommand
  {

    #region INPUT Parameters
    public HgRepository Repository { get; set; }
    public string Command { get; set; }
    public List<string> CommandParameters { get; protected set; }
    #endregion

    #region OUTPUT Parameters
    public string Error { get; set; }
    public List<string> ErrorResultList { get; protected set; }
    public List<string> OutputResultList { get; protected set; }
    #endregion


    /// <summary>
    /// Should the output results of the command be shown to the user.
    /// </summary>
    public bool ShowOutput { get; set; }

    public HgCommand()
    {
      this.ErrorResultList = new List<string>();
      this.OutputResultList = new List<string>();
      this.CommandParameters = new List<string>();
    }

    public bool Execute()
    {
      // confirm that the Repository.WorkingDirectory exists
      if (this.Repository != null
        && !string.IsNullOrEmpty(this.Repository.WorkingDirectory)
        && Directory.Exists(this.Repository.WorkingDirectory))
      {

        this.ErrorResultList.Clear();
        this.OutputResultList.Clear();

        //
        // TODO: assemble the command to execute
        //
        string command = this.BuildCommand();

        ProcessStartInfo processStartInfo = new ProcessStartInfo(HgConstants.HgCommand, command);
        processStartInfo.WorkingDirectory = this.Repository.WorkingDirectory;
        processStartInfo.CreateNoWindow = true;
        processStartInfo.RedirectStandardOutput = true;
        processStartInfo.RedirectStandardError = true;
        // We must set this to FALSE in order to redirect standard IO
        processStartInfo.UseShellExecute = false;
        Process process = new Process();
        process.StartInfo = processStartInfo;
        process.EnableRaisingEvents = true;
        //process.Exited += new EventHandler(executionProcess_Exited);
        process.ErrorDataReceived += new DataReceivedEventHandler(executionProcess_ErrorDataReceived);
        process.OutputDataReceived += new DataReceivedEventHandler(executionProcess_OutputDataReceived);
        process.Start();
        process.BeginErrorReadLine();
        process.BeginOutputReadLine();

        //
        // NOTE: The following command will hang the UI indefinitely
        //
        // http://stackoverflow.com/questions/63303/c-how-do-i-know-when-the-last-outputdatareceived-has-arrived
        //
        //process.WaitForExit();
      }
      else
      {
        this.Error = "Invalid repository working directory.";
        return false;
      }

      return false;
    }


    #region Internal Process Event Handlers

    //private void executionProcess_Exited(object sender, EventArgs e)
    //{
    //  //this.OnCommandOutputReceived("PROCESS_EXITED");
    //}

    private void executionProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
      if (e.Data == null)
      {
        // END
        //this.ParseResults();
      }
      else
      {
        this.ErrorResultList.Add(e.Data);
        this.OnCommandErrorReceived(e.Data);
      }
    }

    private void executionProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
      if (e.Data == null)
      {
        // END
        this.ParseResults();
      }
      else
      {
        this.OutputResultList.Add(e.Data);
        this.OnCommandOutputReceived(e.Data);
      }
    }

    #endregion Internal Process Event Handlers


    public event HgCommandEvent CommandErrorReceived;
    public event HgCommandEvent CommandOutputReceived;
    public event HgCommandEvent CommandExecutionCompleted;


    #region OnEvent methods

    private void OnCommandErrorReceived(string message)
    {
      if (this.CommandErrorReceived != null)
      {
        this.CommandErrorReceived(this, message);
      }
    }

    private void OnCommandOutputReceived(string message)
    {
      if (this.CommandOutputReceived != null)
      {
        this.CommandOutputReceived(this, message);
      }
    }

    protected void OnCommandExecutionCompleted(string message)
    {
      if (this.CommandExecutionCompleted != null)
      {
        this.CommandExecutionCompleted(this, message);
      }
    }

    #endregion



    /// <summary>
    /// Assemble the string to pass to HG.exe from the command name and the parameters.
    /// </summary>
    /// <returns></returns>
    public string BuildCommand()
    {
      StringBuilder stringBuilder = new StringBuilder();
      stringBuilder.Append(this.Command);
      foreach (string parameter in this.CommandParameters)
      {
        stringBuilder.Append(" ");
        //check for SPACE characters and wrap the string if appropriate
        if (parameter.Contains(" "))
        {
          if (!parameter.StartsWith("\""))
          {
            stringBuilder.Append("\"");
          }
          stringBuilder.Append(parameter);
          if (!parameter.EndsWith("\""))
          {
            stringBuilder.Append("\"");
          }
        }
        else
        {
          stringBuilder.Append(parameter);
        }
      }
      return stringBuilder.ToString();
    }


    public virtual void ParseResults()
    {
      this.OnCommandExecutionCompleted("HgCommand.ParseResults");
    }

  }

}